import math
import asyncio
from datetime import datetime, timedelta
from typing import List

import pytz

from client import es_client
from client.redis_client import r, get_redis_client
from dao.es_dao.card_dao import update_doc_by_id, get_card, update_card_location, update_online_status
from dao.es_dao.p_clock_record_dao import mapping_full_data_new, add_clock_record, \
    mapping_clock_record_new, get_clock_record, get_clock_record_mongo
from dao.mongo_dao.location import save_location_mongo
from model.es.card import ElectronicCardModel, ClockRule, ClockRuleTime
from model.wristband_models import LocationData
from service.battery import get_intensity_redis
from utils.date_utils import DateTimeCombiner, date_time_to_str_000z, timestamp_to_str_000z
from utils.geo_utils import is_point_in_geo_fence, calculate_distance


async def location_handle_new(location: LocationData):
    """优化的位置处理函数，使用并发操作提高性能"""
    # 步骤1: 获取卡片信息（必须先执行）
    card_doc: ElectronicCardModel = await get_card(location.device_id)
    if card_doc is None:
        return None
    
    _id = card_doc.es_id
    
    # 步骤2: 并发执行独立操作
    online_task = update_online_status(location.timestamp, _id)
    location_task = update_card_location(location, _id)
    fence_task = get_in_fence(location.device_id, location, card_doc, refresh=True)
    
    _, _, in_fence = await asyncio.gather(online_task, location_task, fence_task)
    
    # 步骤3: 计算距离（只在不在围栏时执行）
    distance = 0
    if not in_fence and card_doc.fence:
        distance = await calculate_distance(location, card_doc.fence[0].points)
    
    # 步骤4: 并发执行最终操作
    fence_update_task = update_in_fence(location, in_fence)
    mongo_save_task = save_location_mongo(in_fence, location, distance)
    clock_save_task = save_clock_in_new(card_doc, location, location.timestamp, in_fence)
    
    await asyncio.gather(fence_update_task, mongo_save_task)
    state = await clock_save_task
    
    # 步骤5: 最后Redis操作（考虑异步化）
    await _async_redis_hset('clock_state', location.device_id, state)
    
    return card_doc


async def _async_redis_hset(hash_name: str, key: str, value):
    """异步Redis hset操作包装器"""
    try:
        # 使用线程池执行同步Redis操作
        await asyncio.to_thread(r.hset, hash_name, key, value)
    except Exception as e:
        import logging
        logger = logging.getLogger(__name__)
        logger.error(f"Redis hset failed: {e}")


async def update_in_fence(location: LocationData, in_fence):
    # 获取所有围栏
    """
        更新围栏状态，是否在围栏中。
        并更新最后一次在围栏中的时间
    :param location:
    :param in_fence:
    :return:
    """
    try:
        # 获取所有围栏
        # in_fence为true 则为1 ，不为true 则为0
        is_in_fence = 1 if in_fence else 0
        await _async_redis_hset("in_fence", location.device_id, is_in_fence)
        r.hset("last_in_fence_timestamp", location.device_id, location.timestamp)
    except Exception as e:
        print(f"Error occurred while updating in fence status: {e}")


async def get_in_fence(imei: str, point: LocationData, card: ElectronicCardModel, refresh: bool = False):
    """
     判断定位是否在围栏中，先从更新的缓存中获取，
    :param refresh:
    :param imei:
    :param point:
    :param card:
    :return:
    """

    if refresh:
        if card is None:
            _id, card = await get_card(imei)
        return is_point_in_geo_fence(point.longitude, point.latitude, card.fence[0].points)
    else:
        in_fence_redis_cache = r.hget("in_fence", imei)
        if in_fence_redis_cache is None:
            if card is None:
                _id, card = await get_card(imei)
            return is_point_in_geo_fence(point.longitude, point.latitude, card.fence[0].points)
        else:
            if in_fence_redis_cache == b'0':
                return False
            if in_fence_redis_cache == b'1':
                return True
            return None


# 写一个根据IMEI，和当前定位，获取当前是否能打卡，并保存当天考勤记录


async def save_clock_in_new(card: ElectronicCardModel, location: LocationData, current_timestamp: int,
                            in_fence: bool) -> int:
    """
        保存打卡记录
        :param current_timestamp:
        :param in_fence:    是否在围栏内
        :param timestamp:    打卡时间戳
        :param card:          打卡人信息
        :param location:       打卡位置信息
        :return: state 打卡状态 0:未打卡 1:未打卡完 2:打卡完
    """
    punch_in_time = datetime.fromtimestamp(current_timestamp)  # 获取打卡时间
    clock_role = card.clock_rule  # 获取人员信息中的打卡规则
    is_early = await is_early_than_next_day_last_role(punch_in_time, clock_role)  # 判断打卡时间是否早于含跨天规则的下班时间，以判断应该获取哪天的打卡记录
    delta_day_value = 1 if is_early else 0
    clock_record, clock_state, start_of_day = await get_last_record_es(card.imei, clock_role, punch_in_time,
                                                                       delta_day=delta_day_value)
    intensity = await get_intensity_redis(card.imei)

    if clock_state == 0:  # 当天未打卡,则还不存在打卡记录，需要和明细一起添加
        if intensity.get('intensity') > 0:
            new_record_list, clock_state = await build_record(card, [], clock_role, punch_in_time, location, in_fence,
                                                              0, start_of_day.date())
            if len(new_record_list) > 0:  # 说明是正常上班状态
                dt_result = await get_clock_dt(new_record_list)
                clock_int_full_record = await mapping_full_data_new(dt_result, new_record_list, card)
                es = await es_client.get_es_client()
                await es.index_document('p_clock_record', clock_int_full_record)
                if len(new_record_list) == 1:
                    return 1
                else:
                    return 2
            else:
                return 0
        else:
            return 0
    elif clock_state == 1:  # 当天未打卡完
        local_tz = pytz.timezone('Asia/Shanghai')
        clock_record_date = clock_record[0]['source']['dt']
        last_role_date = DateTimeCombiner.parse_iso_to_date(clock_record_date)
        clock_record_detail = clock_record[0]['source']['clockRecord']
        if len(clock_record_detail) < len(clock_role[0].rule_time):  # 如果打卡记录少于打卡规则，则判断是否到下一个打卡记录时间段。
            index = math.floor(len(clock_record_detail) / 2)
            new_record_list, state = await build_record(card, clock_record_detail, clock_role, punch_in_time, location,
                                                        in_fence, index, last_role_date)
            if len(new_record_list) > 0:
                for new_record in new_record_list:
                    await add_clock_record(new_record, clock_record[0]['_id'])
                return 2
            else:
                # 正常上班阶段，可能存在出围栏的情况。并且redis里是有数据的。
                return 1
        else:
            return 2
    else:
        return 2


async def get_clock_dt(new_record):
    role_time = new_record[0]['ruleTime']
    punch_in_time = new_record[0]['punchInTime']
    dt_result = await replace_date_time(punch_in_time, role_time)
    return dt_result
    # utc_time = datetime.strptime(dt_result, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=tz.utc)
    # # 2. 转换为目标时区（示例：北京时间）
    # beijing_tz = pytz.timezone('Asia/Shanghai')
    # return datetime.fromtimestamp(utc_time.timestamp()-28800, beijing_tz)  # 处理时间时产生了偏移需要减去


async def build_record(card, clock_record_detail, clock_role, current_time, data, in_fence, index, last_role_date):
    """
      构建打卡记录时间子数组，是存在es index p_clock_record 的 clockRecord 数组中
    :param card:
    :param clock_record_detail:
    :param clock_role:
    :param current_time:
    :param data:
    :param in_fence:
    :param index:
    :param last_role_date:
    :return:
    """
    # 获取当前所处打卡时间段的上下班时间考勤规则时间戳timestamp 值
    role = clock_role[0].rule_time[(int(index) * 2)]
    early, base, after = await get_role_range(last_role_date, role)
    role_1 = clock_role[0].rule_time[(int(index) * 2) + 1]
    early1, base1, after1 = await get_role_range(last_role_date, role_1)
    # 初始化 新的打卡记录数组 和 考勤状态
    new_record = []
    clock_state = 0
    current_timestamp = current_time.timestamp()
    if len(clock_record_detail) % 2 == 0:  # 如果打卡记录是偶数条，则判断上班打卡。是否正常，晚则迟到
        if in_fence:
            clock_state_str = '正常'
            if early < current_timestamp < after:  # 正常上班打卡时段
                clock_state_str = '正常'
                new_record.append(await mapping_clock_record_new(data, role, clock_state_str))
                clock_state = 1
            elif base < current_timestamp < base1 - 3600:  # 下班前一小时之前到场算迟到，超过算缺卡。
                clock_state_str = '迟到'
                new_record.append(await mapping_clock_record_new(data, role, clock_state_str))
                clock_state = 1
            elif base1 - 3600 <= current_timestamp:  # 缺卡
                clock_state_str = '缺卡'
                new_record.append(await mapping_clock_record_new(data, role, clock_state_str))
                new_record.append(await mapping_clock_record_new(data, role_1, clock_state_str))

            # 存两条缺卡
        # 早，不打卡， 在中间，正常。晚了到下班前，迟到，到下班点没来。缺卡一条保存
    else:
        if early1 < current_timestamp < after1:  # 正常下班打卡时段
            # 早，不打卡， 在中间，正常。晚了到下班前，迟到，到下班点没来。找之前的数据早退
            if in_fence:
                clock_state_str = '正常'
                new_record.append(await mapping_clock_record_new(data, role_1, clock_state_str))
                clock_state = 1
        elif (after1 + 900) < current_timestamp:  # 没打下班卡，超过了下班15分钟，还在范围外
            # 找最后一条范围内记录的时间
            if in_fence:
                clock_state_str = '正常'
            else:
                clock_state_str = '早退'
            last_in_fence_timestamp = r.hget("last_in_fence_timestamp", card['imei'])
            if last_in_fence_timestamp is not None:
                data['timestamp'] = int(last_in_fence_timestamp)
            new_record.append(await mapping_clock_record_new(data, role_1, clock_state_str))
        elif current_timestamp < early1:
            clock_state = 1
    return new_record, clock_state


async def get_role_range(last_role_date, role: ClockRuleTime):
    clock_in_time_str = role.clock_in_time
    clock_in_time = datetime.strptime(clock_in_time_str, '%H:%M').time()
    base_role_timestamp = datetime.combine(last_role_date, clock_in_time).timestamp()
    if role.next_day:
        base_role_timestamp += 86400
    return base_role_timestamp - role.before_time / 1000, \
           base_role_timestamp, base_role_timestamp + role.after_time / 1000


async def get_last_record(imei: str, clock_role: dict, current_time: datetime, delta_day: int = 0):
    # 获取clock_role 最后一个元素
    start_of_day = current_time.replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=delta_day)
    end_of_day = current_time.replace(hour=23, minute=59, second=59, microsecond=999999)
    clock_record = await get_clock_record_mongo(start_of_day, end_of_day, [imei])
    # 判断最后打卡记录是否是今天的
    clock_state = 0
    if len(clock_record) > 0:
        local_tz = pytz.timezone('Asia/Shanghai')
        clock_record_date = clock_record[0]['dt']
        clock_record_date_has_timezone = clock_record_date.astimezone(local_tz)
        same_date = (clock_record_date_has_timezone.date() == current_time.date())
        if len(clock_record[0]['clockRecord']) == len(clock_role) * 2:  # 最后一条打卡记录已经打完卡。
            if same_date:
                clock_state = 2  # 当天已经打完卡，后续不用再做任何考勤操作。
            else:
                clock_state = 0
        elif len(clock_record[0]['clockRecord']) < len(clock_role) * 2:  # 最后一条打卡记录没有打完卡。
            clock_state = 1
    else:
        clock_state = 0

    return clock_record, clock_state, start_of_day


async def get_last_record_es(imei: str, clock_role: List[ClockRule], current_time: datetime, delta_day: int = 0):
    # 获取clock_role 最后一个元素
    start_of_day = current_time.replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=delta_day)
    end_of_day = current_time.replace(hour=23, minute=59, second=59, microsecond=999999)
    clock_record = await get_clock_record(start_of_day, end_of_day, [imei])
    # 判断最后打卡记录是否是今天的
    clock_state = 0
    if len(clock_record) > 0:
        local_tz = pytz.timezone('Asia/Shanghai')
        clock_record_date = DateTimeCombiner.parse_iso_to_date(clock_record[0]['source']['dt'])
        same_date = (clock_record_date.date() == current_time.date())
        if len(clock_record[0]['source']['clockRecord']) == len(clock_role[0].rule_time):  # 最后一条打卡记录已经打完卡。
            if same_date:
                clock_state = 2  # 当天已经打完卡，后续不用再做任何考勤操作。
            else:
                clock_state = 0
        elif len(clock_record[0]['source']['clockRecord']) < len(clock_role[0].rule_time):  # 最后一条打卡记录没有打完卡。
            clock_state = 1
    else:
        clock_state = 0

    return clock_record, clock_state, start_of_day


async def is_early_than_next_day_last_role(punch_in_time, clock_role: List[ClockRule]):
    # 先判断是否在隔天打卡的时间段内。
    last_role_time = clock_role[-1].rule_time[1]
    if last_role_time.next_day:
        base_time = datetime.strptime(last_role_time.clock_in_time, '%H:%M').replace(year=punch_in_time.year,
                                                                                     month=punch_in_time.month,
                                                                                     day=punch_in_time.day)
        threshold_time = base_time + timedelta(milliseconds=last_role_time.after_time)
        return threshold_time > punch_in_time
    else:
        return False


async def replace_date_time(punch_in_time, role_time):
    # 解析原始时间（含时区）
    # 解析目标时间
    t = datetime.strptime(role_time, '%H:%M:%S').time()
    # 替换时间部分并清除微秒
    punch_in_time_date = DateTimeCombiner.parse_iso_to_date(punch_in_time)
    new_dt = punch_in_time_date.replace(
        hour=t.hour,
        minute=t.minute,
        second=t.second,
        microsecond=0
    )

    # 生成 ISO 8601 格式字符串（自动处理时区）
    result = date_time_to_str_000z(new_dt)
    return result
